#include "./gaussianblur.h"
#include <math.h>

#define PI 3.14159265358979323846

GaussianBlur::GaussianBlur()
{
}

GaussianBlur::~GaussianBlur()
{
}

void GaussianBlur::BlurImage(char* src, char* dst, int cx, int cy, float sigma, int dim)
{
	m_src = src;
	m_dst = dst;
	m_width = cx;
	m_height= cy;
	m_sigma = sigma;
	m_dim = dim;

	if (dim % 2 == 0)
		dim++;

	char* tmp = new char[cx * cy * 4];
	float* convkernel = GaussianKernel1D();

	Convolve1DWidth(convkernel, src, tmp);
	Convolve1DHeight(convkernel, tmp, dst);

	delete convkernel;
	delete tmp;
}

float* GaussianBlur::GaussianKernel1D()
{
	float* kern = new float[m_dim];
	float s2 = m_sigma * m_sigma;
	int c = m_dim / 2;
	float m = 1.0 / (sqrt(2.0 * PI) * m_sigma);

	double v;
	for (int i = 0; i < (m_dim + 1) / 2; i++)
	{
		v = m * exp(-(1.0 * i * i) / (2.0 * s2));
		kern[c + i] = v;
		kern[c - i] = v;
	}

    double sum = 0;
    for (int i = 0; i < m_dim; i++)
    {
        sum += kern[i];
    }

    double avg = 1.0 / sum;
    for (int i = 0; i < m_dim; i++)
    {
        kern[i] *= avg;
    }

	return kern;
}

GaussianBlur::GaussianColor GaussianBlur::ConvolveLocWidth(float* kernel, char* src, int x, int y)
{
	float pixel_red = 0., pixel_green = 0., pixel_blue = 0.;
	GaussianColor* tmpColor = (GaussianColor*)src;

	int cen = m_dim / 2;
	for (int i = 0; i < m_dim; i++)
	{
		int col = x + (i - cen);
		if (col < 0)
			col = 0;
		if (col >= m_width)
			col = m_width - 1;
		GaussianColor* tmpColor = (GaussianColor*)(src) + m_width * y + col;

		pixel_red += kernel[i] * tmpColor->red;
		pixel_red = pixel_red > 255 ? 255 : pixel_red;

		pixel_green += kernel[i] * tmpColor->green;
		pixel_green = pixel_green > 255 ? 255 : pixel_green;

		pixel_blue += kernel[i] * tmpColor->blue;
		pixel_blue = pixel_blue > 255 ? 255 : pixel_blue;
	}

    pixel_blue += 0.5;
    pixel_green += 0.5;
    pixel_red += 0.5;

	GaussianColor pixel = {(int)pixel_blue, (int)pixel_green, (int)pixel_red, 255};
	return pixel;
}

void GaussianBlur::Convolve1DWidth(float* kernel, char* src, char* dst)
{
	for (int j = 0; j < m_height; j++)
	{
		for (int i = 0; i < m_width; i++)
		{
			GaussianColor pixel = ConvolveLocWidth(kernel, src, i, j);
			GaussianColor* tmpColor = (GaussianColor*)(dst) + m_width * j + i;
			*tmpColor = pixel;
		}
	}
}

GaussianBlur::GaussianColor GaussianBlur::ConvolveLocHeight(float* kernel, char* src, int x, int y)
{
	float pixel_red = 0., pixel_green = 0., pixel_blue = 0.;
	GaussianColor* tmpColor = (GaussianColor*)src;

	int cen = m_dim / 2;
	for (int j = 0; j < m_dim; j++)
	{
		int row = y + (j - cen);
		if (row < 0)
			row = 0;
		if (row >= m_height)
			row = m_height - 1;
		GaussianColor* tmpColor = (GaussianColor*)(src) + m_width * row + x;

		pixel_red += kernel[j] * tmpColor->red;
		pixel_red = pixel_red > 255 ? 255 : pixel_red;

		pixel_green += kernel[j] * tmpColor->green;
		pixel_green = pixel_green > 255 ? 255 : pixel_green;

		pixel_blue += kernel[j] * tmpColor->blue;
		pixel_blue = pixel_blue > 255 ? 255 : pixel_blue;
	}

    pixel_blue += 0.5;
    pixel_green += 0.5;
    pixel_red += 0.5;

	GaussianColor pixel = {(int)pixel_blue, (int)pixel_green, (int)pixel_red, 255};
	return pixel;
}

void GaussianBlur::Convolve1DHeight(float* kernel, char* src, char* dst)
{
	for (int j = 0; j < m_height; j++)
	{
		for (int i = 0; i < m_width; i++)
		{
			GaussianColor pixel = ConvolveLocHeight(kernel, src, i, j);
			GaussianColor* tmpColor = (GaussianColor*)(dst) + m_width * j + i;
			*tmpColor = pixel;
		}
	}
}